home *** CD-ROM | disk | FTP | other *** search
- BTP.DOC
- (C) 1991 John C. Leon
- Documentation for BTP V1.5, 11/9/91
- BTP - The Btrieve Unit for Turbo Pascal 6.0
- (* ------------------------------------------------------------------------ *)
-
-
- INTRODUCTION TO BTP
- -------------------
-
- Btrieve is a record manager sold by Novell. Btrieve has been around a long
- time. It takes many forms today...with versions for Dos, Windows 3.0,
- OS/2, and a "client/server" form when used with Novell Netware. This unit has
- been tested only with Btrieve for Dos, Version 5.10a, with all patches
- available thru 10/27/91 applied.
-
- Btrieve files have no inherent structure to differentiate fields. Your own
- code provides that functionality. The BTP product is the only programmer's
- toolkit of its kind (to my knowledge) available to TP/Btrieve programmers
- offering an extra-ordinarily simple means of Btrieve file management...a
- well-thought out design of data structures and a complete guide (through the
- example programs) to deal with Btrieve files as objects. Further, BTP
- provides the means to make any Btrieve call SIMPLY and with CONFIDENCE, with
- encapsulation of your file's position block. The most intriguing capability
- for many will be the ability to use the relatively new get/step extended calls
- WITH EASE; it is these more complex, yet important functions, that daunt even
- the most ardent fans of Btrieve. NO MORE!
-
- The "universal" Btrieve call, which is made by calling a TP function supplied
- by Novell with the Btrieve product, takes six parameters. Keeping track of
- multiple Btrieve files and the variables for all six parameters for each
- file in a complex application using traditional programming techniques takes
- discipline and a masochistic attention to detail; I consider myself
- disciplined but I'm NOT masochistic. Btrieve programming screams out for the
- simplification possible with object-oriented programming and the record
- structures provided by Turbo Pascal 6.0.
-
- The BTP product was born to remove the drudgery from Btrieve programming. BTP
- includes a number of sound data structures/objects, including these four:
- TRecMgr, BFile, BFixed, and BFileExt. TRecMgr and BFile are base objects;
- they are direct descendants of TP6's TObject. BFixed and BFileExt are both
- direct descendants of BFile.
-
- TRECMGR is of limited usefulness (see the unit source code and VERSION.PAS),
- ------- but it does provide the ability to make non-file oriented calls
- (stop, reset, version, et al).
-
- BFILE is what you'll use for virtually all standard, fixed length Btrieve
- ----- files when not using extended calls; it will probably be your
- workhorse.
-
- BFIXED has structures to support ANY standard, fixed length Btrieve file.
- ------ BFixed is used in the CRUNCHx.PAS programs provided, which can 'clone
- and squish' (remove dead space) from any standard, fixed length
- Btrieve file. BFixed is useful for working on standard fixed length
- files of an unknown nature when you need to perform *record* oriented
- functions with no care about field definitions.
-
- BFILEEXT is used for any Btrieve file for which you'll be using extended calls
- -------- (note there is no specific support for the extended insert call, but
- CRUNCH2.PAS and CRUNCH3.PAS contain examples of how to make such
- calls).
-
- Quite simply, your TYPE declarations define your Btrieve files as descendants
- of one of the three file-oriented objects provided in BTP and described above.
- Those declarations can include definitions of your file's field structures,
- and provide the required data and key buffers. These programmer-defined
- additions to the BTP objects are encapsulated in the descendant object, along
- with the data fields inherited, which include a description of the file's
- structure and the file's position block. Immediately upon instantiation of a
- BTP descendant object, you have access to any file's structure and stats. You
- also have an 'isolated' position block and the required buffers for Btrieve
- calls.
-
- In the case of a BFile descendant, you override (replace) a single object
- method, and can make your Btrieve calls with just two parameters: the Btrieve
- op code and key number. Use of the 4 get/step extended calls is only
- marginally more difficult. In order to use extended calls, your object must
- be a descendant of BFileExt, and must override two methods: one for standard
- calls, and one for the extended calls. BFileExt includes pointers to two
- collections, which are automatically initialized when the object's
- constructor is called. The first collection is for the filter logic terms,
- the second is for the field extractor specs. Your program simply inserts
- items into these collections. The rest, including determining buffer lengths
- and structuring the outgoing buffer, is handled internally by the object's
- methods.
-
- Note that this unit's initialization section does a HALT if the Btrieve
- record manager isn't resident...you never have to code the test yourself.
- Just use this unit!
-
-
- USING THE BFILE OBJECT
- ----------------------
-
- This unit gives you a technique, a shorthand way to get into Btrieve files
- and manipulate them. For example, to open a file and encapsulate its stats
- into your Btrieve file object, you need only do:
-
- ObjectName^.Init(BDosFileName, Normal)
- (BDosFileName is a valid Btrieve file name, and 'Normal' is the open
- mode...see BTP.PAS for the various constants defined.)
-
- Similary, to close a file, you simply do:
-
- BStatus := ObJectName^.Close;
-
- The BFile object defined herein is the heart of this unit. BFile is NOT an
- abstract object!! It can be used as is, for example, if all you want to do
- is to get stats (see STATS.PAS). However, if you wish to do any normal
- record-oriented functions, you will want to extend the object by adding one
- data element, a free-union variant record, to the BFile definition. This
- record will contain your field definitions. Lastly, you are *required* to
- override the BT function. Here's an example.
- ..............................................................................
- TYPE
-
- MyFields = record
- case integer of
- 1: (Field1 : array[1..4] of char; (obviously use size and type)
- Field2 : array[1..10] of char; (of your fields here! )
- (rest of fields go here also)
- KeyBuf : array[1..4] of char); (size to largest key length )
- 2: (DBuffer: array[1..14] of char);(size to record length )
- 3: (Position: array[1..2] of word);(high word returned first! )
- end; (useful after a GET POSITION)
-
- PMyObject = ^MyObject;
- MyObject = object(BFile) (MyObject is now a descendant of BFile. )
- Fields : MyFields;
- function BT(OpCode, Key: integer); integer; virtual;
- end;
-
- VAR
-
- MyFile : PMyObject;
-
-
- (This is the required override of the BT function for all standard, fixed
- length files. As the base object has no field structures, it cannot include
- this function...you must override it by REPLACING it as shown here.)
-
- function MyObject.BT(OpCode, Key:integer);integer;
- begin (DBufferLen is reset here as it may need to)
- DBufferLen := Specs.RecLen; (be changed on return from some ops. )
- BT := Btrv(OpCode, PosBlk, Fields, DBufferLen, Fields.KeyBuf, Key);
- end;
- ..............................................................................
-
- The record variable itself should be used as the Btrieve data buffer
- parameter. The data buffer length is reset to the file's record length before
- making the call. The KeyBuf field of the MyFields record should be used in
- all Btrieve calls as the Btrieve key buffer.
-
- As you can see, your BFile descendant will incorporate the universal Btrieve
- function call in a compact, elegant format. Thanks to OOP and encapsulation,
- you are guaranteed that each descendant's own key and data buffers are used
- since each object has its private copy of the .BT method.
-
- As an aside, the .BT function cannot be fully incorporated into the base BFile
- object since the base object has no data fields corresponding to your file's
- fields, and thus cannot contain a properly sized key buffer or data buffer.
- The override is necessary because neither the BFile object nor Btrieve itself
- know anything about fields or the appropriate size for your buffers.
-
- In fact, to assure you don't call the .BT function of BFile directly, BFile.BT
- includes a call to TP6's Abstract method, which will crash your program with a
- runtime error unless you replace it, presumably with the function as defined
- and recommended here.
-
- For your reference, the base BFile object is all of 975 bytes in size. The
- BFile descendant used in EXAMPLE1.PAS, even with its additional methods and
- data fields, is only 1025 bytes in size. In contrast, the BFixed object is
- 5320 bytes in size, due to its inclusion of maximum size buffers for a file
- with records of up to the maximum size of 4090 and maximum key length of 255.
-
- Assume a Btrieve file named 'DosFile'. To initialize the BFile descendant
- defined above:
-
- MyFile := new(PMyObject, Init('DosFile', Normal));
-
- The BFile constructor/initialization method will do a Btrieve open operation,
- using the open mode you provide as the second parameter to the Init call.
- After the open operation, a stat operation is performed. The Btrieve
- filespec and key specs, retrieved by the stat call, are incorporated into
- BFile's data fields. Those stats can then be read out at will simply by
- referencing the object's data fields in the following manner:
-
- MyFile^.Specs.PageSize, or MyFile^.NumRecs, etc.
-
- The stat fields defined in the object include the number of keys, the total
- number of key segments, the page size, the number of records in the file
- when the INIT was performed, the file flags...in short, the complete Btrieve
- filespec. All other stats can be derived from the object's data fields if
- needed. If you ever need to refresh the stat data to refresh the record
- count, for example, you could simply:
-
- BStatus := MyFile^.Close;
- dispose(MyFile, Done);
- MyFile := new(PMyObject, Init('DosFile', Normal));
- HowMany := MyFile^.NumRecs;
-
- Of course, you could always do a Stat call directly and deal with the
- results, but the BTP way is so EASY!! MyFile^.NumRecs, a longint, is
- available immediately after the Init call.
-
- Btrieve operations with BTP are performed by calling the BT function with JUST
- TWO PARAMETERS. Here's a simple step next call:
-
- BStatus := MyFile^.BT(BStepNext, 2);
-
- (BStatus is a public integer var from this unit. BStepNext is a public
- constant. A number of public constants are defined in this unit that can
- help make your code more readable.)
-
- There is some nominal overhead in the BFile object. It allocates space for
- the maximum of 24 keys/segments, and for an alternate collating sequence,
- whether one is used in the file or not. This simply means a maximum of 633
- bytes per open file could be wasted. This maximum would apply to a standard
- Btrieve file that had just one unsegmented key. The figure of 368 is derived
- as follows:
-
- 665 maximum bytes if the Btrieve max of 24 keys/
- segments and an alternate collating sequence is used
- - 16 bytes to hold the Btrieve file specs (stats)
- - 16 bytes for the minimum required 1 Btrieve key spec
- ---
- 633 bytes maximum possible waste
-
- In addition, my convention of supplying the BFile descendant with a built-in
- key-buffer, arbitrarily sized to the length of the largest size key, could be
- overhead. I don't mind that a bit, and I don't think you should. In my opinion,
- the code space saved by using this unit's structures and techniques versus
- the code resulted from using any other method is significant and more than
- offsets some truly nominal memory costs.
-
- The benefits of referencing any open Btrieve file and its components or stats
- by name makes using the Btrieve record manager a snap. Add to your descendant
- appropriate methods to get/set your fields and the key and data buffers and
- you'll be much more productive.
-
- Your app's code is responsible for stuffing key values into the key buffer,
- getting or setting the data buffer, et al, as usual. However, you gain the
- ease of accessing all Btrieve ops in a distinct shorthand, accessing those
- buffers and fields by name, and can rest assured that all parameters are
- passed and filled. See EXAMPLE1.PAS for a full working model of this
- technique.
-
- You will note that the examples sometimes break one of OOP's rules by
- accessing data fields directly. In my own apps I will typically have at
- least a couple of additional methods in my BFile descendants for getting and
- setting the key buffers and data buffers...such as the following three:
-
- (pass whatever string you want to stuff in the key buffer)
- procedure MyObject.SetKeyBuf(KeyBufString:string);
- var
- Counter,
- LengthKeyBufString : integer;
- begin
- LengthKeyBufString := length(KeyBufString);
- if LengthKeyBufString > 12 then (replace '12' with length of your KeyBuf)
- LengthKeyBufString := 12;
- move(KeyBufString[1], Fields.KeyBuf, LengthKeyBufString);
- if LengthKeyBufString < 12 then
- for Counter := (LengthKeyBufString+1) to 12 do
- Fields.KeyBuf[Counter] := ' '; (pad with trailing blanks)
- end;
-
- function MyObject.GetKeyBuf:string;
- begin
- GetKeyBuf := Fields.KeyBuf;
- end;
-
- function MyObject.GetDBuffer:string;
- begin
- GetDBuffer := Fields.DBuffer;
- end;
-
-
- USING THE BFILEEXT OBJECT
- -------------------------
-
- There were several assumptions coded into the BTP unit to make a working
- structure for the get/step extended calls (see the declaration of data types
- for BFileExt in the unit's source code):
-
- 1. That the required data buffer will never be more than 32767 bytes.
- 2. That values used for a filter's logic terms will never be longer
- than 255 bytes.
-
- Beyond these assumptions, which you can change if necessary by changing the
- unit's source or by defining your own descendants, BTP enables you to make any
- of the four get/step extended calls with ease. The best examples of how to do
- so are in the example programs CRUNCH3.PAS and EXAMPLE2.PAS.
-
- Admittedly, constructing outgoing buffers for Btrieve's extended calls can
- be frustrating. Btrieve is hostile in this regard. BTP reduces this
- drudgery to a couple of additional setup statements!
-
- Recall that the outgoing buffer for these extended calls requires several
- data structures occupying contiguous bytes in the buffer: a header, a
- filter, optional filter logic terms, an extractor, and at least one field
- extractor spec. After initializing the data buffer, you must calculate
- the buffer length for the Btrieve call to be the larger of the outgoing or
- incoming buffers! SHEESH! The BTP way of dealing with this is as follows:
-
- 1: HEADER is handled internally. Don't mess with it. Yes, even setting
- the buffer length in the header is handled internally.
- 2: FILTER's fields MUST be assigned by your program.
- 3: FILTERSPEC is a TP collection that may or may not hold any objects,
- depending on your program's needs.
- 4: EXTRACTOR's fields MUST be assigned by your program.
- 5: EXTRACTORSPEC is a TP collection that must hold at least one object.
- 6: EXTDBUFFER, the data buffer for these 4 extended calls, is handled
- internally. Don't mess with it, except to use it as shown below and in
- EXAMPLE2.PAS when you override BFileExt.BTExt.
- 7. Buffer structuring is totally transparent to your program. Simply
- override the BFileExt.BTExt function as shown, and you're there!
- Believe me, this took some work! The standard TP6 'ForEach' iterator
- is used to take every item in the two collections and account for
- them in structuring the outgoing data buffer.
-
- The BFileExt.BTExt method must be overridden, with the override calling the
- ancestor; i.e. your override must be of a standard form, and must *first* call
- BFileExt.BTExt. This is because BFileExt.BTExt is what sets the buffer length
- and constructs the buffer. This is in contrast to BFile.BT or BFileExt.BT,
- which must be overridden by REPLACING them.
-
- Since Btrieve's get/step extended calls permit use of filters based on field
- values or on a user (program) supplied value, there are two constructors for
- the logic term object: INITF (initialize to compare with field) and,
- INITV (initialize to compare with value).
- Refer to the BTP source code for a list of the parameters required by these
- two constructors.
-
- I strongly urge you to review EXAMPLE2.PAS for a full working model of using
- extended calls with BTP. The following is a skeletal example.
-
- .............................................................................
- USES BTP;
-
- TYPE
- MyFields = record
- case integer of
- 1: (First :array[1..10] of char;
- Last :array[1..20] of char;
- KeyBuf :array[1..20] of char); (sized to largest key)
- 2: (DBuffer :array[1..30] of char); (sized to rec length)
- 3: (Position:array[1..2] of word); (useful after a GET POS)
- end;
- PMyObject = ^MyObject;
- MyObject = object(BFileExt)
- Fields: MyFields;
- function BT(OpCode, Key: integer): integer; virtual;
- function BTExt(OpCode, Key: integer): integer; virtual;
- end;
-
- VAR
- MyFile = PMyObject;
- Counter = integer;
- X = string;
- Value = TCharArray; {TCharArray is a data type in BTP...an array of
- 255 chars.}
-
- {NOTE that you can use these overrides verbatim for each and every descendant
- if you follow the naming conventions outlined in this documentation and in
- the example programs.}
-
- function MyObject.BT(OpCode, Key: integer): integer;
- begin
- DBufferLen := Specs.RecLen;
- BT := Btrv(OpCode, PosBlk, Fields, DBufferLen, Fields.KeyBuf, Key);
- end;
-
- function MyObject.BTExt(OpCode, Key: integer): integer;
- begin
- BStatus := BFileExt.BTExt(OpCode, Key); (MUST call ancestor method!!!)
- BTExt := Btrv(OpCode, PosBlk, ExtDBuffer^.Entire, DBufferLen,
- Fields.KeyBuf, Key);
- end;
-
- BEGIN
-
- MyFile := new(PMyObject, Init('Example', ReadOnly));
- {WHAM! File 'Example' is now open in read only mode, with it's structure and
- stats encapsulated in fields of the object.}
-
- with MyFile^ do {Perform required data initializations.}
- begin {This one code section takes care of }
- Filter.MaxSkip := 50; {initializing the filter and extractor.}
- Filter.NumLogicTerms := 2;
- Extractor.NumRecords := 5;
- Extractor.NumFields := 1;
- end;
-
- {Now specify filter logic terms. We'll setup filter to use 'Leon' (a Last
- Name) as a value for filtering.}
- X := 'Leon';
- for Counter := 1 to length(X) do
- Value[Counter] := X[Counter];
- for Counter := (length(X) + 1) to 255 do
- Value[Counter] := ' '; {Pad the array w/trailing blanks.}
-
- {Now use one statement to initialize the filter logic term object and insert
- it into the FilterSpec collection. Note use of the INITV constructor vs
- the INITF constructor.}
-
- with MyFile^.FilterSpec^ do
- insert(new(PFilterSpec, InitV(BString, 20, 10, Equal, NextTermAnd, Value)));
-
- {Now setup another logic term to specify a first name search condition!}
- X := 'John';
- for Counter := 1 to length(X) do
- Value[Counter] := X[Counter];
- for Counter := (length(X) + 1) to 255 do
- Value[Counter] := ' ';
-
- {Here's the statement to initialize the second filter logic term object and
- insert it into the FilterSpec collection provided and initialized for you.}
-
- with MyFile^.FilterSpec^ do
- insert(new(PFilterSpec, InitV(BString, 10, 0, Equal, LastTerm, Value)));
-
- {Let's set up to extract an entire record, not just particular fields.}
- {1st parameter to the Init is the length of the item to extract (here the
- record length). 2nd parameter is offset from which to begin extraction}
- with MyFile^.ExtractorSpec^ do
- insert(new(PExtSpec, Init(MyFile^.Specs.RecLen, 0)));
-
- {Oh, yea...need to establish a position before using these calls, so let's do
- a Get First before the extended call!}
- BStatus := MyFile^.BT(BGetFirst, Zero);
-
- {Now let's get on with it and make the extended call...}
- BStatus := MyFile^.BTExt(BGetNextExt, Zero);
-
- {Take advantage of the data types and Pascal record structures provided to
- see how many records were returned in this call.}
- writeln('Number of records returned with name ''John Leon'' is ',
- MyFile^.ExtDBuffer^.NumRecs);
-
- {Don't forget to close the file and dispose of the dynamic object. The
- object's Done destructor will also dispose of the collections for you.}
-
- BStatus := MyFile^.Close;
- dispose(MyFile, Done);
-
- END.
-
- .............................................................................
-
- This should be enough to get you started using BTP with both non-extended
- and the get/step extended calls. Again, refer to the unit's source code and
- to the example programs for a fuller understanding of the BTP structures and
- technique.
-
-
- ABOUT THE EXAMPLE PROGRAMS
- --------------------------
-
- The README.1ST file for a list and description of the example programs. They
- should be extremely helpful in getting you up to speed with the BTP product.
- Several of them are utility programs you can use "out of the box"!
-
-
- PARTING SHOTS
- -------------
-
- ENJOY BTP PROGRAMMING! BTP is not free...it is shareware. Yours with no
- obligation for a 30 day trial period, you are expected to remit the $25
- registration fee if you use BTP. Please refer to the README.1ST file for
- license terms and remittance instructions.
-
- The most current version of BTP can usually be found in 3 places on Compu-
- serve if not available to you through your favorite BBS or shareware catalog:
-
- Borland's Programmer's Forum (go BPROGA), in download library 1
- IBM Programmer's Forum (go IBMPRO), in download library 0 (new uploads) or
- download library 5 (other languages)
- Novell Netwire (go NOVA), download library 12 (independent developers),
- download library 14 (public domain),
- download library 17 (other new uploads)
-
- Even if you choose not to register your copy of BTP, I would like to hear
- from you about your experiences with and reaction to using this unit. In
- addition, of course, constructive criticism and suggestions are always
- welcome.
-
- John C. Leon
- 3807 Wood Gardens Court
- Kingwood, TX 77339
-
- 713-359-3641 (residence)
- CIS #72426,2077
-